#!/usr/bin/env python

"""sput.py
Simple Python Unit Testing
Module to help in unit testing. Allows defining and testing unit_tests.
This module is aimed for automated unit testing.
"""
__author__ = "Kashif Iftikhar"
__version__ = "0.1"

class unit_test:
   """Basic class defining a unit test object. Each unit test object contains
      test_name, function to be called, arguments to the function, pass return value
      and fail return value for the function.
      
      The test() method of this class tests the unit case.
      
      TODO: Allow pass_value and fail_value to be conditions.
   """
   def __init__(self, test_name, function_name, pass_value, args):
      """Constructor. Initializes the object and stores given arguments as object memebers."""
      self.test_name = test_name
      self.function_name = function_name
      self.pass_value = pass_value
      self.args = args
      
   def test(self):
      """Calls the fuction with arguments and compares return value of the function to the 
      pass_value and fail_value provided during object initialization. If pass_value matches
      the given value, return True, else if fail_value matches, returns False other wise
      returns False and prints error message that unexpected value was returned.
      """
      
      ret = apply(self.function_name, self.args)
      
      if self.pass_value == ret:
         return True
      else:
         print "Fail value: " + repr(ret)
         return False
      

class tester:
   """The Base Testing Class.
   This class manages individual unit tests and allows running all tests, keeping track 
   of Successes and Failures. Aims at automating the unit testing process.
   """
   def __init__(self):
      """Constructor. Initializes the test_cases list object and sets default
         display width to 80 columns (for prettier formatting). This value can be 
         changed after object creation by altering the display_width member."""
      #Using list for test_cases instead of dictionary because dictionary is sorted by key
      #and not by the sequence of adding which causes bad output display for run_all_tests().
      #We want tests to be run in order which they were added.
      self.display_width = 80
      self.test_cases = []
      
   def add_test(self, test_name, function_name, pass_value, *args):
      """Adds a unit test to the list of tests"""
      t = unit_test(test_name, function_name, pass_value, args)
      self.test_cases.append(t)
      
   def del_test(self, test_name):
      """Deletes given test case"""
      for i in range(len(self.test_cases)-1,-1, -1):
         if test_name == self.test_cases[i].test_name:
            del self.test_cases[i]
            break
   
   def run_all_tests(self):
      """Runs all the tests displaying progress as each test proceeds. Also keeps track
         of SUCCESSFULL and FAILED tests. Displays summary at the end of testing and 
         return True if all tests succeeded else returns False.
      """
      
      total_tests = len(self.test_cases)
      failed_tests = 0
      passed_tests = 0
      
      all_passed = True
      
      w = self.display_width - 4 #padding of two cols on each side.
      
      print "="*self.display_width
      s = "TOTAL UNIT TESTS: %i" % total_tests
      print s.center(self.display_width)
      print "-"*self.display_width
      
      for test in self.test_cases:
         test_name = test.test_name
         s = "  Running Test: %s " % test_name
         p = w - len(s)
         p -= 9 # for either [PASSED] or [FAILED] string.
         pad = "."*p
         s += pad
         
         if True == test.test():
            s += " [PASSED]"
            passed_tests += 1
         else:
            all_passed = False
            s += " [FAILED]"
            failed_tests += 1
            
         print s
         
      #display summary here.
      print "-"*self.display_width
      print "SUMMARY".center(self.display_width)
      print "  Total Tests  : %i" % total_tests
      print "  Tests Passed : %i" % passed_tests
      print "  Tests Failed : %i" % failed_tests
      print
      if all_passed:
         print "ALL TESTS PASSED".center(self.display_width)
      else:
         print "SOME TESTS FAILED".center(self.display_width)
         
      print
      print "="*self.display_width
      return all_passed      

   
if __name__ == '__main__':
   
   def math_func(val1, val2, op):
      if '+' == op:
         return val1+val2
      elif '-' == op:
         return val1-val2
      elif '*' == op:
         return val1*val2
      elif '/' == op:
         return val1/val2
      else:
         return "UNKNOWN OPERATOR"
      
   def compare_func(val1, val2):
      if val1 == val2:
         return True
      else:
         return False
      
        
   t = tester()
   
   t.add_test("Comparision Test 1", compare_func, True,  4, 4)
   t.add_test("Comparision Test 2", compare_func, False, 5, 4)
   t.add_test("Comparision Test 3", compare_func, True, 5, 4)
   t.add_test("Comparision Test 4", compare_func, False, 4, 4)
   
   t.add_test("Addition Test 1", math_func, 2, 1, 1, '+')
   t.add_test("Addition Test 2", math_func, 2, 4, 1, '+')
   t.add_test("Substraction Test 1", math_func, 2, 4, 2, '-')
   t.add_test("Substraction Test 2", math_func, 2, 6, 1, '-')
   t.add_test("Multiplication Test 1", math_func, 4, 2, 2, '*')
   t.add_test("Multiplication Test 2", math_func, 2, 4, 1, '*')
   t.add_test("Division Test 1", math_func, 2, 4, 2, '/')
   t.add_test("Division Test 2", math_func, 2, 4, 1, '/')
   
   t.add_test("Unsupported op Test", math_func, 2, 4, 2, '%')
   t.run_all_tests()
   
   t.del_test("Unsupported op Test")
   t.del_test("Addition Test 2")
   t.del_test("Substraction Test 2")
   t.del_test("Multiplication Test 2")
   t.del_test("Division Test 2")
   t.run_all_tests()
   
   t.del_test("Comparision Test 3")
   t.del_test("Comparision Test 4")
   t.run_all_tests()
   
   
